/*-
 * Copyright (c) 2009-2010 David Forsythe.
 * See LICENSE file for license details.
 */

#include <stdlib.h>
#include <string.h>

#include "internal.h"
#include "property.h"

/*
 * pkg_property_*() functions and the struct pkg_property object implement a
 * simple node based property list system.  A property list is a set of nodes
 * representing different types of data.  This property list implementation
 * supports:
 *      PLBOOL - A boolean value.
 *      PLDATA - Arbitrary data.
 *      PLDATA - A time value.
 *      PLDICT - A dictionary of properties.
 *      PLLIST - An iteratable list of properties.
 *      PLNUMBER - A signed integer.
 *      PLSTRING - A c string.
 *
 * When calling pkg_property_*_create(), the passed in root node may be either a
 * PLDICT or a PLLIST type node.  If the root is a PLDICT, the new node will be
 * added to its dictionary.  If the root node is a PLLIST, the new node will be
 * appended to its list.
 */

/* TODO: Set field to sort list on. */

/*
 * This macro gives us a safe creation function of any type other than PLDICT 
 * and PLIST, which need their root.*_head fields initialized in their create 
 * functions.
 */
#define PKG_PROPERTY_CREATE(fnname, type, type_name)                           \
struct pkg_property *                                                          \
fnname(struct pkg_property *pn, const char *name)                              \
{                                                                              \
        struct pkg_property *nn;                                               \
        if (pn != NULL) {                                                      \
                if (pn->info.property_type != PLDICT &&                        \
                        pn->info.property_type != PLLIST) {                    \
                        PKG_CLIENT_CRASH(__func__, "Root node is not a"        \
                        " PLDICT or PLLIST.");                                 \
                }                                                              \
                if (pn->info.property_type == PLDICT &&                        \
                        pkg_property_dict_get_property(pn, name) != NULL) {    \
                        return (NULL);                                         \
                }                                                              \
        }                                                                      \
        nn = pkg_property_create();                                            \
        if (nn == NULL) {                                                      \
                return (NULL);                                                 \
        }                                                                      \
        nn->info.property_type = type;                                         \
        nn->info.property_name = name;                                         \
        nn->info.property_type_name = type_name;                               \
        nn->root_set = 0;                                                      \
        if (pn != NULL) {                                                      \
                if (pn->info.property_type == PLDICT) {                        \
                        SPLAY_INSERT(property_dict, &pn->root.dict_head, nn);  \
                } else {                                                       \
                        TAILQ_INSERT_TAIL(&pn->root.list_head, nn,             \
                                entry.list_entry);                             \
                        pn->sorted = 0;                                        \
                }                                                              \
        }                                                                      \
        return (nn);                                                           \
}


static int __pkg_property_compare(struct pkg_property *, struct pkg_property *);

/* Tree init. */
SPLAY_PROTOTYPE(property_dict, pkg_property, entry.dict_entry,
        __pkg_property_compare);
SPLAY_GENERATE(property_dict, pkg_property, entry.dict_entry,
        __pkg_property_compare);

/* Compare two properties by property_name. */
static int
__pkg_property_compare(struct pkg_property *p1, struct pkg_property *p2)
{
        return (strcmp(p1->info.property_name, p2->info.property_name));
}

/*
 * Create and initalize a new blank property object.
 *
 * Returns the new property object.  Returns NULL on system error.
 */
struct pkg_property *
pkg_property_create(void)
{
        struct pkg_property *pp;

        pp = calloc(1, sizeof(*pp));
        if (pp == NULL) {
                return (NULL);
        }
        pp->info.property_type = PLNONE;
        pp->info.property_name = NULL;
        pp->info.property_type_name = "None";
        pp->root_set = 0;
        pp->sort = 0;
        pp->sorted = 0;
        pp->cursor = NULL;
        return (pp);
}

/*
 * Recursively free all the memory used by a node and all of its sub nodes.  Any
 * parents nodes to this root will not be aware of its release, so this function
 * should only be called on absolute roots.
 */
void
pkg_property_release(struct pkg_property *pn)
{
        struct pkg_property *nn;
        struct pkg_property *fn;
        
        if (!pn->root_set && pn->info.property_type != PLLIST &&
                pn->info.property_type != PLDICT) {
                /* We never set anything in this node, just free it. */
                free(pn);
                return;
        }

        switch(pkg_property_get_type(pn)) {
        case (PLLIST):
                fn = TAILQ_FIRST(&pn->root.list_head);
                while (fn != NULL) {
                        nn = TAILQ_NEXT(fn, entry.list_entry);
                        pkg_property_release(fn);
                        fn = nn;
                }
                TAILQ_INIT(&pn->root.list_head);
                break;
        case(PLDICT):
                fn = SPLAY_MIN(property_dict, &pn->root.dict_head);
                while (fn != NULL) {
                        nn = SPLAY_NEXT(property_dict, &pn->root.dict_head, fn);
                        SPLAY_REMOVE(property_dict, &pn->root.dict_head, fn);
                        pkg_property_release(fn);
                        fn = nn;
                }
                SPLAY_INIT(&pn->root.dict_head);
                break;
        case (PLSTRING):
                free(pn->root.string);
                break;
        case (PLDATA):
                free(pn->root.data.data);
                break;
        default:
                break;
        }

        /* Finally, free the structure. */
        free(pn);
}

const char *
pkg_property_get_name(struct pkg_property *pn)
{
        return (pn->info.property_name);
}

property_type_t
pkg_property_get_type(struct pkg_property *pn)
{
        return (pn->info.property_type);
}

const char *
pkg_property_get_type_name(struct pkg_property *pn)
{
        return (pn->info.property_type_name);
}

struct pkg_property *
pkg_property_get_next_property(struct pkg_property *pn, struct pkg_property *ln)
{
        (void)pn;
        (void)ln;

        /* XXX: Need a parent pointer. */

        PKG_CLIENT_CRASH(__func__, "Not implemented.");

        return (NULL);
}

/* 
 * Create a dictionary in root node pn.  If pn is NULL, this is a root node.  If
 * pn isn't NULL, put the new node (nn) into pn's dictionary.
 *
 * Return the new property node.  Returns NULL on system error, and crashes if
 * it gets invalid input.
 */
struct pkg_property *
pkg_property_dict_create(struct pkg_property *pn, const char *name)
{
        struct pkg_property *nn;
        
        if (pn != NULL) {
                if (pn->info.property_type != PLDICT && 
                        pn->info.property_type != PLLIST) {
                        PKG_CLIENT_CRASH(__func__, 
                                "Root node is not a PLDICT or PLLIST.");
                }
                if (pn->info.property_type == PLDICT &&
                        pkg_property_dict_get_property(pn, name) != NULL) {
                        /* Collision. */
                        return (NULL);
                }
        }

        /* Allocate a new property. */
        nn = pkg_property_create();
        if (nn == NULL) {
                return (NULL);
        }
        
        /* Set property variables. */
        nn->info.property_type = PLDICT;
        nn->info.property_name = name;
        nn->info.property_type_name = "Dictionary";

        /* Initialize the tree head. */
        SPLAY_INIT(&nn->root.dict_head);
        nn->root_set = 1;

        /* If pn != NULL, add nn to its tree or list. */
        if (pn != NULL) {
                if (pn->info.property_type == PLDICT) {
                        SPLAY_INSERT(property_dict, &pn->root.dict_head, nn);
                } else {
                        TAILQ_INSERT_TAIL(&pn->root.list_head, nn,
                                entry.list_entry);
                        pn->sorted = 0;
                }
        }

        return (nn);
}

/*
 * Retrieve a property node from a PLDICT node by name.  Return NULL if the node
 * cannot be found in pn.
 */
struct pkg_property *
pkg_property_dict_get_property(struct pkg_property *pn, const char *name)
{
        struct pkg_property kn;

        if (pn->info.property_type != PLDICT) {
                PKG_CLIENT_CRASH(__func__, 
                        "Root node is not a PLDICT.");
        }
        if (name == NULL) {
                PKG_CLIENT_CRASH(__func__, "invalid input: name is NULL.");
        }

        kn.info.property_name = name;
        return (SPLAY_FIND(property_dict, &pn->root.dict_head, &kn));
}

struct pkg_property *
pkg_property_dict_get_next_property(struct pkg_property *pn, 
        struct pkg_property *ln)
{
        struct pkg_property *nn;

        if (pn->info.property_type != PLDICT) {
                PKG_CLIENT_CRASH(__func__, "Root node is not a PLDICT.");
        }

        if (ln == NULL) {
                nn = SPLAY_MIN(property_dict, &pn->root.dict_head);
        } else {
                nn = SPLAY_NEXT(property_dict, &pn->root.dict_head, ln);
                if (nn == NULL) {
                        return (NULL);
                }
        }

        return (nn);
}

struct pkg_property *
pkg_property_list_create(struct pkg_property *pn, const char *name)
{
        struct pkg_property *nn;

        if (pn != NULL) {
                if (pn->info.property_type != PLDICT &&
                        pn->info.property_type != PLLIST) {
                        PKG_CLIENT_CRASH(__func__, 
                                "Root node is not a PLDICT or PLLIST.");
                }
                if (pkg_property_dict_get_property(pn, name) != NULL) {
                        /* Collision. */
                        return (NULL);
                }
        }

        /* Create and initialize the new node. */
        nn = pkg_property_create();
        if (nn == NULL) {
                return (NULL);
        }
        nn->info.property_type = PLLIST;
        nn->info.property_name = name;
        nn->info.property_type_name = "List";
        nn->sort = 0;
        nn->sorted = 0;
        nn->cursor = NULL;
        
        /* Initialize tree head. */
        TAILQ_INIT(&nn->root.list_head);
        nn->root_set = 1;

        if (pn != NULL) {
                if (pn->info.property_type == PLDICT) {
                        SPLAY_INSERT(property_dict, &pn->root.dict_head, nn);
                } else {
                        TAILQ_INSERT_TAIL(&pn->root.list_head, nn,
                                entry.list_entry);
                        pn->sorted = 0;
                }
        }

        return (nn);
}

/*
 * Get the next property node in a PLLIST.
 * 
 * Return the next property node after ln in the list property pn.  If ln is
 * NULL, return the first property node in pn.  If the end of the list has been
 * reached, return NULL.
 */
struct pkg_property *
pkg_property_list_get_next_property(struct pkg_property *pn, 
        struct pkg_property *ln)
{
        struct pkg_property *nn;

        if (pn->info.property_type != PLLIST) {
                PKG_CLIENT_CRASH(__func__, "Root node is not a PLLIST");
        }

        if (ln == NULL) {
                nn = TAILQ_FIRST(&pn->root.list_head);
        } else {
                nn = TAILQ_NEXT(ln, entry.list_entry);
                if (nn == NULL) {
                        return (NULL);
                }
        }

        return (nn);
}

/* 
 * Do a merge sort on a PLLIST node.  Returns the PLLIST node with the contents
 * sorted.
 */
struct pkg_property *
pkg_property_list_sort(struct pkg_property *pn, 
        int (*compare) (struct pkg_property *, struct pkg_property *))
{
        struct pkg_property *nn;
        struct pkg_property *fn;
        struct pkg_property list_a, list_b;
        struct pkg_property *ptr_a, *ptr_b;
        int (*cmp) (struct pkg_property *, struct pkg_property *);
        
        if (pn->info.property_type != PLLIST) {
                PKG_CLIENT_CRASH(__func__, "Root node is not a PLLIST.");
        }

        if (pn->sorted) {
                return (pn);
        }

        fn = TAILQ_FIRST(&pn->root.list_head);
        if (fn == NULL || TAILQ_NEXT(fn, entry.list_entry) == NULL) {
                return (pn);
        }

        /* Initialize the two sub lists. */
        list_a.info.property_type = PLLIST;
        list_b.info.property_type = PLLIST;
        TAILQ_INIT(&list_a.root.list_head);
        TAILQ_INIT(&list_b.root.list_head);
        list_a.sorted = 0;
        list_b.sorted = 0;
       
        /* Split. */
        fn = TAILQ_FIRST(&pn->root.list_head);
        /* This inits nn for the loop. */
        nn = TAILQ_NEXT(fn, entry.list_entry);
        if (fn != NULL) {
                TAILQ_REMOVE(&pn->root.list_head, fn, entry.list_entry);
                TAILQ_INSERT_TAIL(&list_a.root.list_head, fn, entry.list_entry);
        }
        while (nn != NULL) {
                fn = TAILQ_FIRST(&pn->root.list_head);
                nn = TAILQ_NEXT(nn, entry.list_entry);
                if (nn != NULL) {
                        nn = TAILQ_NEXT(nn, entry.list_entry);
                } else {
                        break;
                }
                TAILQ_REMOVE(&pn->root.list_head, fn, entry.list_entry);
                TAILQ_INSERT_TAIL(&list_a.root.list_head, fn, entry.list_entry);
        }

        /* XXX: Fix this so that we don't need to do the concat (just sort into
         * pn). */

        /* Put what's left in pn into list_b. */
        TAILQ_CONCAT(&list_b.root.list_head, &pn->root.list_head,
                entry.list_entry);

        if (compare == NULL) {
                cmp = __pkg_property_compare;
        } else {
                cmp = compare;
        }
        
        /* Recurse. */
        ptr_a = pkg_property_list_sort(&list_a, cmp);
        ptr_b = pkg_property_list_sort(&list_b, cmp);
        
        /* Merge. */

        while (!TAILQ_EMPTY(&ptr_a->root.list_head) &&
                !TAILQ_EMPTY(&ptr_b->root.list_head)) {
                fn = TAILQ_FIRST(&ptr_a->root.list_head);
                nn = TAILQ_FIRST(&ptr_b->root.list_head);
                if (cmp(fn, nn) < 0) {
                        TAILQ_REMOVE(&ptr_a->root.list_head, fn, 
                                entry.list_entry);
                        TAILQ_INSERT_TAIL(&pn->root.list_head, fn,
                                entry.list_entry);
                } else {
                        TAILQ_REMOVE(&ptr_b->root.list_head, nn,
                                entry.list_entry);
                        TAILQ_INSERT_TAIL(&pn->root.list_head, nn,
                                entry.list_entry);
                }
        }
        
        /* Add on any stragglers. */
        if (!TAILQ_EMPTY(&ptr_a->root.list_head)) {
                TAILQ_CONCAT(&pn->root.list_head, &ptr_a->root.list_head,
                        entry.list_entry);
        }
        if (!TAILQ_EMPTY(&ptr_b->root.list_head)) {
                TAILQ_CONCAT(&pn->root.list_head, &ptr_b->root.list_head,
                        entry.list_entry);
        }

        pn->sorted = 1;
        pn->cursor = NULL; /* Same as _sequence_rewind() */
        
        /* Give back pn. */
        return (pn);
}

struct pkg_property *
pkg_property_list_sequence_get_next_property(struct pkg_property *pn)
{
        struct pkg_property *peek;

        if (pn->info.property_type != PLLIST) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLLIST");
        }

        if (pn->cursor == NULL) {
                pn->cursor = TAILQ_FIRST(&pn->root.list_head);
        } else {
                peek = TAILQ_NEXT(pn->cursor, entry.list_entry);
                if (peek == NULL) {
                        return (NULL);
                }
                pn->cursor = peek;
        }

        return (pn->cursor);
}

void
pkg_property_list_sequence_rewind(struct pkg_property *pn)
{
        pn->cursor = NULL;
}

void
pkg_property_list_remove_property(struct pkg_property *pn, 
        struct pkg_property *pr)
{
        if (pn->info.property_type != PLLIST) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLLIST");
        }

        TAILQ_REMOVE(&pn->root.list_head, pr, entry.list_entry);
}

/* pkg_property_boolean_*() */

PKG_PROPERTY_CREATE(pkg_property_boolean_create, PLBOOL, "Boolean")

void
pkg_property_boolean_set_boolean(struct pkg_property *pn, int boolean)
{
        if (pn->info.property_type != PLBOOL) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLBOOL.");
        }

        pn->root.boolean = boolean;
}

int
pkg_property_boolean_get_boolean(struct pkg_property *pn)
{
        if (pn->info.property_type != PLBOOL) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLBOOL.");
        }

        return (pn->root.boolean);
}

/* pkg_property_string_*() */

PKG_PROPERTY_CREATE(pkg_property_string_create, PLSTRING, "String");

const char *
pkg_property_string_set_string(struct pkg_property *pn, const char *string)
{
        if (pn->info.property_type != PLSTRING) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLSTRING.");
        }
        
        /* Should be init'd to NULL anyway. */
        free(pn->root.string);
        /* Simple enough. */
        pn->root.string = strdup(string);
        if (pn->root.string == NULL) {
                return (NULL);
        }
        pn->root_set = 1;

        return (pn->root.string);
}

const char *
pkg_property_string_get_string(struct pkg_property *pn)
{
        if (pn->info.property_type != PLSTRING) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLSTRING.");
        }

        return (pn->root.string);
}

/* pkg_property_number_*() */

PKG_PROPERTY_CREATE(pkg_property_number_create, PLNUMBER, "Number");

void
pkg_property_number_set_number(struct pkg_property *pn, int64_t number)
{
        if (pn->info.property_type != PLNUMBER) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLNUMBER.");
        }

        pn->root.integer = number;
}

int64_t
pkg_property_number_get_number(struct pkg_property *pn)
{
        if (pn->info.property_type != PLNUMBER) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLNUMBER.");
        }

        return (pn->root.integer);
}

/* pkg_property_date_*() */

PKG_PROPERTY_CREATE(pkg_property_date_create, PLDATE, "Date");

void
pkg_property_date_set_date(struct pkg_property *pn, time_t date)
{
        if (pn->info.property_type != PLDATE) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLDATE.");
        }

        pn->root.date = date;
        pn->root_set = 1;
}

time_t
pkg_property_date_get_date(struct pkg_property *pn)
{
        if (pn->info.property_type != PLDATE) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLDATE.");
        }

        return (pn->root.date);
}

/* pkg_property_data_*() */

PKG_PROPERTY_CREATE(pkg_property_data_create, PLDATA, "Data");

const void *
pkg_property_data_set_data(struct pkg_property *pn, const void *data, 
        size_t data_size)
{
        if (pn->info.property_type != PLDATA) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLDATA.");
        }
        
        if (pn->root_set) {
                free(pn->root.data.data);
        }

        pn->root.data.data = malloc(data_size + 1);
        if (pn->root.data.data == NULL) {
                pn->root.data.size = -1;
                return (NULL);
        }

        memcpy(pn->root.data.data, data, data_size);
        pn->root.data.size = data_size;
        
        pn->root_set = 1;

        return (pn->root.data.data);
}

const void *
pkg_property_data_read_data(struct pkg_property *pn, int fd, size_t len)
{
        int r;
        // char *hold;

        if (pn->info.property_type != PLDATA) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLDATA.");
        }
        
        /* TODO: Put all this into a data setup macro. */
        if (pn->root_set) {
                free(pn->root.data.data);
        }

        pn->root.data.data = malloc(len + 1);
        if (pn->root.data.data == NULL) {
                pn->root.data.size = -1;
                return (NULL);
        }

        if ((r = read(fd, pn->root.data.data, len)) < 0) {
                pn->root.data.size = -1;
                return (NULL);
        }
#if 0
        /* Eh, I can't really get away with this... */
        hold = pn->root.data.data;
        hold[r - 1] = '\0';
#endif
        
        /* Zero out extra crud. */
        memset((char *)pn->root.data.data + r, '\0', (len - (r - 1)));
        pn->root_set = 1;
        pn->root.data.size = len;

        return (pn->root.data.data);
}

ssize_t
pkg_property_data_get_data(struct pkg_property *pn, const void **data)
{
        if (pn->info.property_type != PLDATA) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLDATA.");
        }

        if (!pn->root_set) {
                data = NULL;
                return (-1);
        }

        *data = pn->root.data.data;
        return (pn->root.data.size);
}
